package com.diy.sigmund.diycommon.spring.config;

import cn.hutool.core.thread.ThreadFactoryBuilder;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.diy.sigmund.diycommon.entity.constant.LogConstant;
import com.diy.sigmund.diycommon.util.json.JacksonUtil;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * 自定义线程池组件，详情查看@EnableAsync接口文档
 * <br>启用 Spring 的异步方法执行功能，类似于 Spring 的<task:*> XML 命名空间中的功能。
 * <br>与Configuration类一起使用，如下所示，为整个Spring应用上下文启用注解驱动的异步处理：
 * <br>   @Configuration
 * <br>   @EnableAsync
 * <br>   public class AppConfig {
 * <br>
 * <br>   }
 * <br>MyAsyncBean是一种用户定义的类型，其中一个或多个方法使用 Spring 的@Async注释、EJB 3.1 @javax.ejb.Asynchronous注释或通过annotation属性指定的任何自定义注释进行注释。方面是透明地为任何注册的 bean 添加的，例如通过这个配置：
 * <br>   @Configuration
 * <br>   public class AnotherAppConfig {
 * <br>
 * <br>       @Bean
 * <br>       public MyAsyncBean asyncBean() {
 * <br>           return new MyAsyncBean();
 * <br>       }
 * <br>   }
 * <br>默认情况下，Spring 将搜索关联的线程池定义：上下文中的唯一org.springframework.core.task.TaskExecutor bean，或者名为“taskExecutor”的java.util.concurrent.Executor bean。如果两者都无法解析，则将使用org.springframework.core.task.SimpleAsyncTaskExecutor来处理异步方法调用。此外，具有void返回类型的带注释的方法不能将任何异常传输回调用者。默认情况下，仅记录此类未捕获的异常。
 * <br>要自定义所有这些，请实现AsyncConfigurer并提供：
 * <br>你自己的Executor通过getAsyncExecutor()方法，和
 * <br>您自己的AsyncUncaughtExceptionHandler通过getAsyncUncaughtExceptionHandler()方法。
 * <br>注意： AsyncConfigurer配置类在应用程序上下文引导的早期初始化。如果您需要对那里的其他 bean 有任何依赖关系，请确保尽可能将它们声明为“惰性”，以便让它们也通过其他后处理器。
 * <br>   @Configuration
 * <br>   @EnableAsync
 * <br>   public class AppConfig implements AsyncConfigurer {
 * <br>
 * <br>       @Override
 * <br>       public Executor getAsyncExecutor() {
 * <br>           ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 * <br>           executor.setCorePoolSize(7);
 * <br>           executor.setMaxPoolSize(42);
 * <br>           executor.setQueueCapacity(11);
 * <br>           executor.setThreadNamePrefix("MyExecutor-");
 * <br>           executor.initialize();
 * <br>           return executor;
 * <br>       }
 * <br>
 * <br>       @Override
 * <br>       public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
 * <br>           return new MyAsyncUncaughtExceptionHandler();
 * <br>       }
 * <br>   }
 * <br>如果只需要自定义一项，可以返回null ，保持默认设置。尽可能考虑从AsyncConfigurerSupport扩展。
 * <br>注意：在上面的例子中， ThreadPoolTaskExecutor不是一个完全托管的 Spring bean。如果您想要一个完全托管的 bean， @Bean注释添加到getAsyncExecutor()方法。在这种情况下，不再需要手动调用executor.initialize()方法，因为它会在 bean 初始化时自动调用。
 * <br>作为参考，可以将上面的示例与以下 Spring XML 配置进行比较：
 * <pre class="code">
 * &lt;beans&gt;
 *
 *     &lt;task:annotation-driven executor="myExecutor" exception-handler="exceptionHandler"/&gt;
 *
 *     &lt;task:executor id="myExecutor" pool-size="7-42" queue-capacity="11"/&gt;
 *
 *     &lt;bean id="asyncBean" class="com.foo.MyAsyncBean"/&gt;
 *
 *     &lt;bean id="exceptionHandler" class="com.foo.MyAsyncUncaughtExceptionHandler"/&gt;
 *
 * &lt;/beans&gt;
 * </pre>
 *
 * <br>上述基于 XML 和基于 JavaConfig 的例子是等价的，只是设置了Executor的线程名前缀；这是因为<task:executor>元素没有公开这样的属性。这演示了基于 JavaConfig 的方法如何通过直接访问实际组件来实现最大的可配置性。
 * <br>mode属性控制如何应用建议：如果模式是AdviceMode.PROXY （默认），那么其他属性控制代理的行为。请注意，代理模式只允许通过代理拦截呼叫；同一类中的本地调用不能以这种方式被拦截。
 * <br>请注意，如果模式设置为AdviceMode.ASPECTJ ，则proxyTargetClass属性的值将被忽略。另请注意，在这种情况下， spring-aspects模块 JAR 必须存在于类路径中，编译时编织或加载时编织将方面应用于受影响的类。这种情况下不涉及代理；本地电话也将被拦截。
 * <br>自从：
 * <br>3.1
 * <br>也可以看看：
 * <br>Async ， AsyncConfigurer ， AsyncConfigurationSelector
 * <br>作者：
 * <br>克里斯·比姆斯、尤尔根·霍勒、斯蒂芬妮·尼科尔、山姆·布兰宁
 *
 * @author ylm-sigmund
 * @see EnableAsync
 * @since 2021/9/10 10:00
 */
@EnableAsync
@Configuration
public class ThreadPoolConfiguration implements AsyncConfigurer {

    /**
     * cpu核数
     */
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final Logger log = LoggerFactory.getLogger(ThreadPoolConfiguration.class);

    /**
     * The Executor instance to be used when processing async method invocations.
     */
    @Bean("customAsyncExecutor")
    @Override
    public Executor getAsyncExecutor() {
        // ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor();
        // threadPoolExecutor.prestartAllCoreThreads();

        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        // poolSizeMonitor
        // threadPool.setCorePoolSize(1);
        // threadPool.setMaxPoolSize(1);
        // threadPool.setQueueCapacity(1);

        threadPool.setCorePoolSize(CPU_COUNT + 1);
        threadPool.setMaxPoolSize(CPU_COUNT + 1);
        threadPool.setQueueCapacity(500);
        // 指定是否允许核心线程超时
        threadPool.setAllowCoreThreadTimeOut(false);
        threadPool.setKeepAliveSeconds(60);
        threadPool.setThreadNamePrefix("customAsyncExecutor-");
        threadPool.setRejectedExecutionHandler(new AbortPolicy());
        // 设置是否在关机时等待计划任务完成，不中断正在运行的任务并执行队列中的所有任务。
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        // 设置此执行程序在关闭时应该阻止的最大秒数，以便在容器的其余部分继续关闭之前等待剩余任务完成执行。如果您的剩余任务可能需要访问也由容器管理的其他资源，这将特别有用。
        threadPool.setAwaitTerminationSeconds(60);

        // threadPool.setTaskDecorator(new MdcTaskDecorator());

        threadPool.initialize();
        log.info(LogConstant.SPRING_LOG, "ThreadPoolConfiguration初始化完成");
        // return threadPool;

        // 异步线程传递上下文，可在异步线程池中获取上下文信息`TransmittableThreadLocal`
        // transmittable-thread-local解决TLog线程池场景的标签传递情况,不再需要MdcThreadPoolTaskExecutor
        Executor ttlExecutor = TtlExecutors.getTtlExecutor(threadPool);
        return ttlExecutor;
    }

    /**
     * @see <a href="https://juejin.cn/post/7035875013412519966">WebAsyncTask</a>
     */
    @Bean("asyncTestExecutor")
    public ThreadPoolTaskExecutor getExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        //核心线程池大小
        threadPool.setCorePoolSize(1);
        //最大线程数
        threadPool.setMaxPoolSize(1);
        //队列容量
        threadPool.setQueueCapacity(0);
        // 活跃时间
        threadPool.setKeepAliveSeconds(300);
        // 线程名字前缀
        threadPool.setThreadFactory(ThreadFactoryBuilder.create()
                .setNamePrefix("asyncTestExecutor-")
                .setUncaughtExceptionHandler((th, throwable) ->
                        log.error("uncaughtException name={},Throwable={}", th.getName(), throwable))
                .build());

        //设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,使异步线程的销毁优先于Redis等其他处理报错
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        //设置线程池中任务的等待时间，如果超过这个时候还没有销毁就强制销毁，以确保应用最后能够被关闭，而不是阻塞住
        threadPool.setAwaitTerminationSeconds(60);
        // setRejectedExecutionHandler：当pool已经达到max size的时候，如何处理新任务
        // CallerRunsPolicy：不在新线程中执行任务，而是由调用者所在的线程来执行
        // threadPool.setRejectedExecutionHandler(new CallerRunsPolicy());
        threadPool.setRejectedExecutionHandler(new AbortPolicy());
        // 该处理会使得业务代码超时
        // threadPool.setRejectedExecutionHandler((r, executor) -> {
        //     throw new DiyRuntimeException(ResponseEnum.REQUEST_FREQUENT_FOR_ASYNC);
        // });
        threadPool.initialize();
        // 异步线程传递上下文，可在异步线程池中获取上下文信息
        // threadPool.setTaskDecorator(runnable -> {
        //     // 获得主线程的上下文
        //     CustomUser customUser = ContextHolder.getInstance().getCustomUser();
        //     log.info("获取主线程的上下文信息={}", JacksonUtil.toJson(customUser));
        //     return () -> {
        //         // 此段代码均在异步线程中
        //         try {
        //             CustomUser context = ContextHolder.getInstance().getCustomUser();
        //             log.info("当前线程的上下文信息={}", JacksonUtil.toJson(context));
        //             ContextHolder.getInstance().setCustomUser(customUser);
        //             log.info("设置主线程的上下文信息到异步线程");
        //             runnable.run();
        //         } finally {
        //             // 进入自定义异常处理器后会走到该代码
        //             ContextHolder.getInstance().remove();
        //             log.info("清除异步线程用户上下文 成功");
        //         }
        //     };
        // });

        log.info(LogConstant.SPRING_LOG, "asyncTestExecutor初始化完成");
        return threadPool;
    }

    /**
     * <br>在返回类型为void的异步方法执行期间引发异常时使用的AsyncUncaughtExceptionHandler实例。
     * <br>优先级高于ThreadFactory中线程定义的setUncaughtExceptionHandler
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        // return new SimpleAsyncUncaughtExceptionHandler();
        return (ex, method, params) ->
                log.error("自定义线程池内部出现异常，将其捕获，异步方法是: [{},{}]", method, JacksonUtil.toJson(params), ex);
    }

}
